import {
  BoxGeometry,
  BufferGeometry,
  CurvePath,
  CylinderGeometry,
  ExtrudeGeometry,
  Float32BufferAttribute,
  LineCurve3,
  Shape,
  ShapeGeometry,
  Vector2,
  Vector3,
} from "three";
import { mergeGeometries } from "three/examples/jsm/utils/BufferGeometryUtils.js";

function cumsum(points) {
  const a = new Vector2();
  const b = new Vector2();
  const res = [];
  for (const pt of points) {
    b.set(pt[0], pt[1]);
    res.push(a.add(b).clone());
  }
  return res;
}

function removeIndicesFromGeometry(geometry) {
  if (!geometry.index) {
    console.log("Geometry already has no indices.");
    return geometry;
  }

  const indexArray = geometry.index.array;
  const positionArray = geometry.attributes.position.array;
  const normalArray = geometry.attributes.normal
    ? geometry.attributes.normal.array
    : null;
  const uvArray = geometry.attributes.uv ? geometry.attributes.uv.array : null;

  const newPositions = [];
  const newNormals = [];
  const newUvs = [];

  for (let i = 0; i < indexArray.length; i++) {
    const index = indexArray[i];

    // 展开位置数据
    newPositions.push(positionArray[index * 3 + 0]);
    newPositions.push(positionArray[index * 3 + 1]);
    newPositions.push(positionArray[index * 3 + 2]);

    // 如果有法线数据，展开法线数据
    if (normalArray) {
      newNormals.push(normalArray[index * 3 + 0]);
      newNormals.push(normalArray[index * 3 + 1]);
      newNormals.push(normalArray[index * 3 + 2]);
    }

    // 如果有UV数据，展开UV数据
    if (uvArray) {
      newUvs.push(uvArray[index * 2 + 0]);
      newUvs.push(uvArray[index * 2 + 1]);
    }
  }

  // 创建新的BufferGeometry
  const newGeometry = new BufferGeometry();

  // 设置新的位置属性
  newGeometry.setAttribute(
    "position",
    new Float32BufferAttribute(newPositions, 3)
  );

  // 如果有法线数据，设置新的法线属性
  if (newNormals.length > 0) {
    newGeometry.setAttribute(
      "normal",
      new Float32BufferAttribute(newNormals, 3)
    );
  }

  // 如果有UV数据，设置新的UV属性
  if (newUvs.length > 0) {
    newGeometry.setAttribute("uv", new Float32BufferAttribute(newUvs, 2));
  }

  return newGeometry;
}

class SolidUAbutment {
  constructor(params = {}) {
    this.params = params;
    this.base = new Vector3(0, this.dY, -this.capHeight / 2);
    this.build();
  }

  get deckWidth() {
    return this.params?.deckWidth || 12.75;
  }

  build() {
    this.buildCap();
    this.buildBackWall();
    this.buildSideWall();
    this.buildStopper();
    this.buildBody();
    this.geometry = mergeGeometries([
      ...[this.geomCap, this.geomBackWall, ...this.geomStoppers].map((g) =>
        removeIndicesFromGeometry(g)
      ),
      ...this.geomSideWalls,
      this.body,
    ]);
  }

  get reverseGeometry() {
    return this.geometry.rotateZ(Math.PI);
  }

  buildCap() {
    const cap = new BoxGeometry(this.deckWidth, this.thickY, this.capHeight);
    cap.translate(...this.base);

    this.geomCap = cap;
  }

  buildBackWall() {
    const backWall = new BoxGeometry(
      this.deckWidth,
      this.backWallY,
      this.backWallH
    );
    const offset = new Vector3(0, -this.backWallY / 2, +this.backWallH / 2);
    backWall.translate(...offset);
    this.geomBackWall = backWall;
  }

  buildSideWall() {
    this.geomSideWalls = [];
    const curvePath = new CurvePath();
    curvePath.add(new LineCurve3(new Vector3(0, -5, 0), new Vector3(0, 0, 0)));

    const offset = new Vector3(
      -this.deckWidth / 2,
      -this.backWallY,
      this.backWallH
    );

    {
      const shape = this.sideWallShapeL;
      const sideWall = new ExtrudeGeometry(shape, {
        bevelEnabled: false,
        extrudePath: curvePath,
      });
      const wall = sideWall.clone().translate(...offset);
      this.geomSideWalls.push(wall);
    }
    {
      const shape = this.sideWallShapeR;
      const sideWall = new ExtrudeGeometry(shape, {
        bevelEnabled: false,
        extrudePath: curvePath,
      });
      offset.x = -(-this.deckWidth) / 2;
      const wall = sideWall.clone().translate(...offset);
      this.geomSideWalls.push(wall);
    }
  }

  buildStopper() {
    this.geomStoppers = [];
    const stopper = new BoxGeometry(
      this.stopperX,
      this.stopperY,
      this.stopperH
    );
    const offsetY = this.stopperY / 2;
    const offsetH = this.stopperH / 2;
    const offsetX = this.deckWidth / 2 - this.stopperX / 2;

    {
      const innerStopper = stopper.clone();
      innerStopper.translate(-offsetX, offsetY, offsetH);
      this.geomStoppers.push(innerStopper);
    }
    {
      const outerStopper = stopper.clone();
      outerStopper.translate(offsetX, offsetY, offsetH);
      this.geomStoppers.push(outerStopper);
    }
  }

  buildBody() {
    this.body = [];
    const dY_front = this.calc_slope_y(this.bodyH, this.body_slope_m);
    const dy_end = this.calc_slope_y(this.bodyH, this.body_slope_n);
    const edge = 0;
    const points = [
      [this.bodyH, this.thickY - this.backWallY - edge + dY_front],
      [-this.bodyH, -dY_front],
      [0, -(this.thickY - edge)],
      [this.bodyH, -dy_end],
    ];
    const shape = new Shape(cumsum(points));

    const curvePath = new CurvePath();
    const dX = -this.deckWidth / 2;
    curvePath.add(
      new LineCurve3(new Vector3(dX, 0, 0), new Vector3(-dX, 0, 0))
    );
    const body = new ExtrudeGeometry(shape, {
      bevelEnabled: false,
      extrudePath: curvePath,
    });
    const offset = new Vector3(0, 0, -this.capHeight);
    body.translate(...offset);
    this.body = body;
  }

  get body_slope_m() {
    let m = this.params?.body_slope_m;
    if (m === undefined) m = 8;
    return m;
  }

  get body_slope_n() {
    return this.sideWallK;
  }

  get columnCount() {
    return 3;
  }
  get columnX() {
    return 4.5;
  }

  get columnDiameter() {
    return 1.5;
  }
  get columnRadius() {
    return this.columnDiameter / 2;
  }

  get columnH() {
    return 5;
  }

  get columnDY() {
    return -this.backWallY + this.thickY / 2;
  }

  get dY() {
    return this.thickY / 2 - this.backWallY;
  }

  get backWallY() {
    return 0.6;
  }

  get stopperY() {
    return this.thickY - this.backWallY;
  }

  get beamH() {
    return 1.2;
  }

  get bearingH() {
    return 0.3;
  }

  get stopperX() {
    return 0.3;
  }

  get stopperH() {
    return 0.5;
  }

  get earWallX() {
    return 0.5;
  }

  get earWallY() {
    return 2.4;
  }
  get earWall_h1() {
    return 0.75;
  }

  get earWallH() {
    return this.totalH - 0.2;
  }

  get bodyH() {
    return 5;
  }

  get sideWallTopX() {
    return 0.5;
  }

  get sideWallTop_h1() {
    return 1;
  }

  get sideWallK() {
    let m = this.params?.side_wall_k;
    if (m === undefined) m = 3;
    return m;
  }

  get sideWallWB() {
    return (
      this.sideWallTopX +
      this.calc_slope_y(this.sideWallH - this.sideWallTop_h1, this.sideWallK)
    );
  }

  calc_slope_y(h, m) {
    return m ? h / m : 0;
  }

  get sideWallShapeR() {
    const points = cumsum([
      [this.sideWallH, this.sideWallWB],
      [0, -this.sideWallWB],
      [-this.sideWallH, 0],
      [0, this.sideWallTopX],
      [this.sideWallTop_h1, 0],
    ]);
    return new Shape(points);
  }
  get sideWallShapeL() {
    const points = cumsum([
      [this.sideWallH, -this.sideWallWB],
      [0, this.sideWallWB],
      [-this.sideWallH, 0],
      [0, -this.sideWallTopX],
      [this.sideWallTop_h1, 0],
    ]);
    return new Shape(points);
  }

  get sideWallH() {
    return this.totalH + this.bodyH;
  }

  get totalH() {
    return this.backWallH + this.capHeight;
  }

  get backWallH() {
    return this.beamH + this.bearingH;
  }

  get capHeight() {
    return 0.8;
  }
  get thickY() {
    return 1.8;
  }
}

export default SolidUAbutment;
